package cc.jq1024.aop;

import cc.jq1024.infrastructure.persistent.redis.IRedisService;
import cc.jq1024.types.annotations.DCCValue;
import cc.jq1024.types.annotations.RateLimiterAccessInterceptor;
import cc.jq1024.types.common.Constants;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.concurrent.TimeUnit;

/**
 * 切面 - 限流，熔断
 * @author li--jiaqiang
 * @date 2024−07−31
 */
@Slf4j
@Aspect
@Component
@RequiredArgsConstructor
public class RateLimiterAop {

    private final IRedisService redisService;

    // 限流开关
    @DCCValue("rateLimiterSwitch:open")
    private String rateLimiterSwitch;

    // 设置切点
    @Pointcut("@annotation(cc.jq1024.types.annotations.RateLimiterAccessInterceptor)")
    public void aopPoint() {}

    @Around("aopPoint() && @annotation(rateLimiterAccessInterceptor)")
    public Object doRouter(ProceedingJoinPoint joinPoint, RateLimiterAccessInterceptor rateLimiterAccessInterceptor) throws Throwable {

        // 0. 限流开关【open 开启、close 关闭】关闭后，不会走限流策略
        if (!"open".equals(rateLimiterSwitch)) {
            log.info("当前未开启接口限流");
            return joinPoint.proceed();
        }
        // 获取限流key
        String property = rateLimiterAccessInterceptor.key();
        if (null == property || property.isEmpty()) {
            throw new RuntimeException("annotation RateLimiter key is null");
        }
        // 获取拦截字段(用户ID)
        String propertyAttr = getPropertyValue(property, joinPoint.getArgs());
        log.info("propertyAttr:{}", propertyAttr);

        String blacklistCacheKey = Constants.RedisKey.RATE_LIMITER_BLACK_LIST_KEY + propertyAttr;
        String perSecondsCacheKey = Constants.RedisKey.RATE_LIMITER_PER_SECONDS + propertyAttr;
        Integer blacklistCacheCount = redisService.getValue(blacklistCacheKey);
        // 黑名单拦截
        if (!"all".equals(propertyAttr) && rateLimiterAccessInterceptor.blacklistCount() > 0 &&
                null != blacklistCacheCount && blacklistCacheCount > rateLimiterAccessInterceptor.blacklistCount()) {
            log.info("限流-黑名单拦截(24h)：{}", propertyAttr);
            return fallbackMethodResult(joinPoint, rateLimiterAccessInterceptor.fallbackMethod());
        }
        // 白名单放行
//        if ("all".equals(propertyAttr)) return joinPoint.proceed();

        // 查询一分钟内的访问次数（频率）
        Integer permitsPerSecond = redisService.getValue(perSecondsCacheKey);
        if (null == permitsPerSecond) {
            // 一分钟内是第一次访问
            redisService.setValue(perSecondsCacheKey, 1, 1, TimeUnit.MINUTES);
        } else {
            if (permitsPerSecond > rateLimiterAccessInterceptor.permitsPerSecond() && rateLimiterAccessInterceptor.permitsPerSecond() > 0) {
                // 访问次数超过限制, 加入黑名单(设置有效期24h)
                if (!redisService.isExists(blacklistCacheKey)) {
                    redisService.setValue(blacklistCacheKey, 1, rateLimiterAccessInterceptor.timeDuration(), rateLimiterAccessInterceptor.timeUnit());
                } else {
                    // 累计访问次数
                    redisService.incr(blacklistCacheKey);
                }
                log.info("限流-超频次拦截（1min）：{}", propertyAttr);
                // 调用拦截方法
                return fallbackMethodResult(joinPoint, rateLimiterAccessInterceptor.fallbackMethod());
            } else {
                // 增加访问次数
                redisService.incr(perSecondsCacheKey);
            }
        }

        return joinPoint.proceed();
    }

    /**
     * 调用用户配置的回调方法，当拦截后，返回回调结果。
     */
    private Object fallbackMethodResult(JoinPoint jp, String fallbackMethod) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        // 获取连接点（JoinPoint）的签名信息。签名通常包含有关连接点所代表的方法的信息
        Signature sig = jp.getSignature();
        // 将签名信息转换为 MethodSignature 对象。MethodSignature 是 Signature 的一个子类，提供了更多关于方法的参数类型的信息。
        MethodSignature methodSignature = (MethodSignature) sig;
        // 从 JoinPoint 对象中获取目标对象（即被代理的对象），
        // 然后使用反射（Reflection）获取目标类中名称为 fallbackMethod 的方法，该方法需要与原方法的参数类型一致。
        Method method = jp.getTarget().getClass().getMethod(fallbackMethod, methodSignature.getParameterTypes());
        // 使用反射机制调用找到的备用方法，传入原方法的实例（jp.getThis()）和参数（jp.getArgs()），并返回方法的执行结果。
        return method.invoke(jp.getThis(), jp.getArgs());
    }



    /**
     * 实际根据自身业务调整，主要是为了获取通过某个值做拦截
     */
    public String getPropertyValue(String attr, Object[] args) {
        if (args[0] instanceof String) {
            return args[0].toString();
        }
        String filedValue = null;
        for (Object arg : args) {
            try {
                if (filedValue != null && !filedValue.isEmpty()) {
                    break;
                }
                // filedValue = BeanUtils.getProperty(arg, attr);
                // fix: 使用lombok时，uId这种字段的get方法与idea生成的get方法不同，会导致获取不到属性值，改成反射获取解决
                filedValue = String.valueOf(this.getValueByName(arg, attr));
            } catch (Exception e) {
                log.error("获取路由属性值失败 attr：{}", attr, e);
            }
        }
        return filedValue;
    }


    /**
     * 获取对象的特定属性值
     *
     * @param item 对象
     * @param name 属性名
     * @return 属性值
     * @author tang
     */
    private Object getValueByName(Object item, String name) {
        try {
            Field field = getFieldByName(item, name);
            if (field == null) {
                return null;
            }
            field.setAccessible(true);
            Object o = field.get(item);
            field.setAccessible(false);
            return o;
        } catch (IllegalAccessException e) {
            return null;
        }
    }

    /**
     * 根据名称获取方法，该方法同时兼顾继承类获取父类的属性
     *
     * @param item 对象
     * @param name 属性名
     * @return 该属性对应方法
     * @author tang
     */
    private Field getFieldByName(Object item, String name) {
        try {
            Field field;
            try {
                field = item.getClass().getDeclaredField(name);
            } catch (NoSuchFieldException e) {
                field = item.getClass().getSuperclass().getDeclaredField(name);
            }
            return field;
        } catch (NoSuchFieldException e) {
            return null;
        }
    }




}